﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Packaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.XPath;

namespace DataService.Office.Word
{
    /// <summary>
    /// 这个类PackageHelper ，官方给出的一个类，这个类基本是可以直接用的。
    /// </summary>
    public class PackageHelper
    {
        #region 私有变量
        private MemoryStream m_packageData;
        private Package m_package;
        #endregion

        #region 构造函数
        /// <summary>
        /// 用给定数据创建一个Package对象的实例
        /// </summary>
        /// <param name="data">用来初始化package的比特数组</param>
        public PackageHelper(byte[] data)
        {
            m_packageData = new MemoryStream();
            m_packageData.Write(data, 0, data.Length);
            // 打开 package
            m_package =
                Package.Open(m_packageData,
                    FileMode.Open,
                    FileAccess.ReadWrite); // 注意，这里要有写权限
            //或者直接打开一个文件
            //m_package = Package.Open("filename",FileMode.Open,FileAccess.ReadWrite)
        }
        #endregion

        /// <summary>
        /// 清理资源
        /// </summary>
        public void Dispose()
        {
            m_package.Close();
            m_packageData.Dispose();
        }

        #region Package 管理方法
        /// <summary>
        /// Creates a relationship from one package part to another.
        /// </summary>
        /// <param name="sourceUri">The uri of the source part.</param>
        /// <param name="targetUri">The relative path representing the location of the target part based on the source part.</param>
        /// <param name="relationshipType">The type of relationship to create.</param>
        /// <returns>The ID of the new relationship.</returns>
        public string CreateInternalRelationship(Uri sourceUri, Uri targetUri, string relationshipType)
        {
            // open the source part
            PackagePart sourcePart = m_package.GetPart(sourceUri);

            // create the relationship
            PackageRelationship relationship =
                sourcePart.CreateRelationship(
                    PackUriHelper.GetRelativeUri(sourceUri, targetUri),
                    TargetMode.Internal, relationshipType);

            // return the new rel id
            return relationship.Id;
        }
        public static string CreateInternalRelationship(Uri sourceUri, Uri targetUri, string relationshipType, Package package)
        {
            // open the source part
            PackagePart sourcePart = package.GetPart(sourceUri);

            // create the relationship
            PackageRelationship relationship =
                sourcePart.CreateRelationship(
                    PackUriHelper.GetRelativeUri(sourceUri, targetUri),
                    TargetMode.Internal, relationshipType);

            // return the new rel id
            return relationship.Id;
        }

        public static PackageRelationship CreateInternalRelationship(Uri sourceUri, Uri targetUri, string relationshipType, string relationshipId, Package package)
        {
            // open the source part
            PackagePart sourcePart = package.GetPart(sourceUri);

            // create the relationship
            PackageRelationship relationship =
                sourcePart.CreateRelationship(
                    PackUriHelper.GetRelativeUri(sourceUri, targetUri),
                    TargetMode.Internal, relationshipType, relationshipId);
            //返回关联关系
            return relationship;
        }


        /// <summary>
        ///保存package到一个文件。
        /// </summary>
        /// <param name="filename">要保存的文件</param>
        public void Save(string filename)
        {
            // flush and data in the package buffers to the stream
            m_package.Flush();
            m_package.Close();

            // write the stream to the output file
            using (FileStream outputStream = File.Create(filename))
                m_packageData.WriteTo(outputStream);

            // close the stream
            m_packageData.Close();
        }
        #endregion

        #region Part Management Methods
        /// <summary>
        /// 检查指定的Part是否存在
        /// </summary>
        /// <param name="partUri">The uri of the part to find.</param>
        /// <returns>A boolean value specifying if the part exists.</returns>
        public bool PartExists(Uri partUri)
        {
            // get the part if it exists
            return m_package.PartExists(partUri);
        }
        /// <summary>
        /// 检查指定的Part是否存在
        /// </summary>
        /// <param name="partUri">The uri of the part to find.</param>
        /// <returns>A boolean value specifying if the part exists.</returns>
        public bool PartExists(Uri partUri, Package package)
        {
            // get the part if it exists
            return package.PartExists(partUri);
        }
        /// <summary>
        /// Creates a new part containing the data provided.
        /// </summary>
        /// <param name="partUri">The uri of the part to create.</param>
        /// <param name="contentType">The content type for the new part.</param>
        /// <param name="data">The data to initially load into the new part.</param>
        /// <returns></returns>
        public PackagePart CreateNewPart(Uri partUri, string contentType, byte[] data)
        {
            if (PartExists(partUri))
                return m_package.GetPart(partUri);
            // create the part
            PackagePart newPart =
                m_package.CreatePart(partUri, contentType, CompressionOption.Normal);
            // write the data into the part
            using (Stream partStream =
                newPart.GetStream(FileMode.Create, FileAccess.Write))
            {
                partStream.Write(data, 0, data.Length);
            }
            // return the new package part
            return newPart;
        }

        public PackagePart CreateNewPart(Uri partUri, string contentType, string fileName)
        {

            if (PartExists(partUri))
                return m_package.GetPart(partUri);
            PackagePart newPart =
                m_package.CreatePart(partUri, contentType, CompressionOption.Normal);
            if (File.Exists(fileName))
            {
                FileStream ifs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
                using (Stream partStream =
                    newPart.GetStream(FileMode.Create, FileAccess.Write))
                {
                    byte[] byteTmp = new byte[1024];
                    int count = 0;
                    while ((count = ifs.Read(byteTmp, 0, 1024)) > 0)
                    {
                        partStream.Write(byteTmp, 0, count);
                    }
                }
            }
            return newPart;
        }
        /// <summary>
        /// Opens the part and loads the XML into an XPathDocument.
        /// </summary>
        /// <param name="partUri">The uri of the part to open.</param>
        /// <returns>Read only XPathDocument containing the xml from the part.</returns>
        public XPathDocument GetReadOnlyPart(Uri partUri)
        {
            // retrieve the part
            PackagePart readOnlyPart = m_package.GetPart(partUri);

            // load the part into a XPathDocument
            using (Stream partStream = readOnlyPart.GetStream(FileMode.Open, FileAccess.Read))
                return new XPathDocument(partStream);
        }

        /// <summary>
        /// Opens the part and loads the XML into an XmlDocument.
        /// </summary>
        /// <param name="partUri">The uri of the part to open.</param>
        /// <returns>XmlDocument containing the xml from the part.</returns>
        public XmlDocument GetWritablePart(Uri partUri)
        {
            // get the part
            PackagePart writablePart = m_package.GetPart(partUri);

            // load the part into a XmlDocument
            XmlDocument partXml = new XmlDocument();
            using (Stream partStream = writablePart.GetStream(FileMode.Open, FileAccess.Read))
                partXml.Load(partStream);

            // return the document
            return partXml;
        }
        public XmlDocument GetWritablePart(Uri partUri, Package package)
        {
            // get the part
            PackagePart writablePart = package.GetPart(partUri);

            // load the part into a XmlDocument
            XmlDocument partXml = new XmlDocument();
            using (Stream partStream = writablePart.GetStream(FileMode.Open, FileAccess.Read))
                partXml.Load(partStream);

            // return the document
            return partXml;
        }
        public XmlReader GetWritablePartAsReader(Uri partUri)
        {
            // get the part
            PackagePart writablePart = m_package.GetPart(partUri);
            // load the part into a XmlReader
            return XmlReader.Create(writablePart.GetStream());
        }

        public Stream GetWritablePartAsStream(Uri partUri)
        {
            // get the part
            PackagePart writablePart = m_package.GetPart(partUri);
            // load the part into a stream
            return writablePart.GetStream();
        }

        /// <summary>
        /// Replaces all content in the part with the XML in the XmlDocument.
        /// </summary>
        /// <param name="partUri">The uri of the part to replace.</param>
        /// <param name="partXml">XmlDocument containing the xml to place into the part.</param>
        public void SavePart(Uri partUri, XmlDocument partXml)
        {
            // get the part
            PackagePart writablePart = m_package.GetPart(partUri);
            // load the part into a XmlDocument
            using (Stream partStream = writablePart.GetStream(FileMode.Open, FileAccess.Write))
            {
                partStream.SetLength(0);
                partXml.Save(partStream);
            }
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Creates a relative uri based on two other relative Uri's.
        /// </summary>
        /// <param name="sourceUri">The uri navigation is starting from.</param>
        /// <param name="targetUri">The uri of the resource to access.</param>
        /// <returns>A relative Uri defining the path from the source Uri to the target Uri.</returns>
        private Uri BuildRelativeUri(Uri sourceUri, Uri targetUri)
        {
            string[] sourceSegments = sourceUri.OriginalString.Split('/');
            string[] targetSegments = targetUri.OriginalString.Split('/');
            // find the number of shared segments
            int sharedSegments = 0;
            int maxSegments = Math.Min(sourceSegments.Length, targetSegments.Length) - 1;
            for (; sharedSegments != maxSegments; sharedSegments++)
                if (sourceSegments[sharedSegments] != targetSegments[sharedSegments])
                    break;
            // build the relative uri
            StringBuilder relativeUri = new StringBuilder();
            for (int i = 0; i != sourceSegments.Length - 1 - sharedSegments; i++)
                relativeUri.Append("../");
            for (int i = sharedSegments; i != targetSegments.Length - 1; i++)
            {
                relativeUri.Append(targetSegments[i]);
                relativeUri.Append("/");
            }
            relativeUri.Append(targetSegments[targetSegments.Length - 1]);

            // return the new relative Uri
            return new Uri(relativeUri.ToString(), UriKind.Relative);
        }
        #endregion
    }
}
